import { _decorator, Vec2, Component, UITransform, Node, Vec3, CCBoolean, log } from "cc";
const { ccclass, property } = _decorator;

import { PathNode, PolygonNavAStar } from "./PolygonNavAStar";
import PolygonNavPolygon from './PolygonNavPolygon';
import PolygonNavData from "./PolygonNavData";
import GeometryUtils from "../../Example/Script/GeometryUtils";
import PolygonDebugLine from "../../Example/Script/PolygonDebugLine";
class Polygon {
	Points: Vec2[];
	UniqueId?: number;
	Bounds?: {
		minX: number;
		maxX: number;
		minY: number;
		maxY: number;
	};
}

class Intersections {
	Point: Vec2;
	UniqueId1: number;
	UniqueId2: number;
	IsValid: boolean;
}

@ccclass('PolygonNav2D')
export default class PolygonNav2D extends Component {
	@property(PolygonNavPolygon)
	AllPolygons: PolygonNavPolygon[] = [];

	@property(UITransform)
	Trans: UITransform = null;

	@property(PolygonNavData)
	PolygonNavData: PolygonNavData = null;

	@property(CCBoolean)
	DebugLine: boolean = false;


	private _allPolygons: Polygon[];
	private _allInflatePolygons: { [key: number]: Polygon };
	private _pathNodes: PathNode[] = [];
	private _intersections: Intersections[] = [];
	private _autoIncrementId: number;

	private _debugLine: PolygonDebugLine;
	private get PolygonDebugLine(): PolygonDebugLine {
		if (!this._debugLine) {
			let node = new Node("DebugLine");
			node.parent = this.node;
			this._debugLine = this.node.addComponent(PolygonDebugLine);
		}
		return this._debugLine;
	}


	protected onLoad(): void {
		this.SetCustomMap(this.AllPolygons);
	}

	public SetCustomMap(allPolygons: PolygonNavPolygon[]) {
		this._allPolygons = [];
		this._allInflatePolygons = {};
		this._pathNodes = [];
		this._intersections = [];
		this._autoIncrementId = 0;
		for (let index = 0; index < allPolygons.length; index++) {
			let polygon = allPolygons[index];
			this.Internal_AddPolygon(polygon);
		}

		this.CreateNodes();
		if (this.PolygonNavData) {
			for (let i = 0; i < this._pathNodes.length; i++) {
				this._pathNodes[i].links = [];
				let links = this.PolygonNavData.PathNodeLinks[i];
				if (links) {
					for (let j = 0; j < links.Array.length; j++) {
						const link = links.Array[j];
						this._pathNodes[i].links.push(link);
					}
				}
				else {
					// if (links == null) {
					// 	console.log("i " + i);
					// }
				}
			}
		}
		else {
			this.LinkNodes(this._pathNodes);
		}
		this.CheckIntersectionsState();
	}

	//自定义数据结构
	public SetCustomMap2(jsonData: any) {
		let allPolygons = jsonData.polygons;
		this._allPolygons = [];
		this._pathNodes = [];
		this._allInflatePolygons = {};
		this._autoIncrementId = 0;
		let count = 0;
		for (let index = 0; index < allPolygons.length; index++) {
			let polygon = allPolygons[index];
			let points: Vec2[] = [];
			for (let m = 0; m < polygon.length; m++) {
				const element = polygon[m];
				count++;
				points.push(new Vec2(element[0], element[1]))
			}
			this.Internal_AddPolygonPoints(points);
		}

		this.CreateNodes();
		if (jsonData.pathNodeLinks) {
			let pathNodeLinks = jsonData.pathNodeLinks;
			for (let i = 0; i < this._pathNodes.length; i++) {
				this._pathNodes[i].links = [];
				let links = pathNodeLinks[i];
				for (let j = 0; j < links.length; j++) {
					const link = links[j];
					this._pathNodes[i].links.push(link);
				}
			}
		}
		else {
			this.LinkNodes(this._pathNodes);
		}
		this.CheckIntersectionsState();
	}

	private CheckIntersectionsState() {
		for (let index = this._intersections.length - 1; index >= 0; index--) {
			let element = this._intersections[index];
			element.IsValid = this.PointIsValid(element.Point);
		}
	}

	public AddPolygon(polygon: PolygonNavPolygon) {
		let id = this.Internal_AddPolygon(polygon);
		this.CreateNodes();
		this.LinkNodes(this._pathNodes);
		this.CheckIntersectionsState();
		return id;
	}

	public AddPolygonPoints(points: Vec2[]): number {
		let id = this.Internal_AddPolygonPoints(points);
		this.CreateNodes();
		this.LinkNodes(this._pathNodes);
		this.CheckIntersectionsState();
		return id
	}

	private Internal_AddPolygon(polygon: PolygonNavPolygon) {
		let points = [];
		let polyPoints = polygon.GetPoints();
		for (let j = 0; j < polyPoints.length; j++) {
			let point = polyPoints[j];
			let worldPos = polygon.Trans.convertToWorldSpaceAR(new Vec3(point.x, point.y));
			points.push(this.Trans.convertToNodeSpaceAR(worldPos));
		}
		return this.Internal_AddPolygonPoints(points);
	}

	private Internal_AddPolygonPoints(points: Vec2[]): number {
		let id = this._autoIncrementId;
		const bounds = this.GetBounds(points);
		let polygon: Polygon = { Points: points, UniqueId: id, Bounds: bounds };
		this._allPolygons.push(polygon);

		let inflatedPoints = GeometryUtils.InflatePolygon(points, 0.1);
		let inflatePolygon = { Points: inflatedPoints, UniqueId: id, Bounds: this.GetBounds(inflatedPoints) };

		//处理相交点
		for (const key in this._allInflatePolygons) {
			this.DealOverlapPolygon(this._allInflatePolygons[key], inflatePolygon);
		}

		this._allInflatePolygons[id] = inflatePolygon;
		this._autoIncrementId++;

		if (this.DebugLine) {
			this.PolygonDebugLine.DebugLine(id, points);
		}

		return id;
	}

	private GetBounds(points: Vec2[]) {
		const bounds = {
			minX: Infinity,
			maxX: -Infinity,
			minY: Infinity,
			maxY: -Infinity
		};

		for (const point of points) {
			bounds.minX = Math.min(bounds.minX, point.x);
			bounds.maxX = Math.max(bounds.maxX, point.x);
			bounds.minY = Math.min(bounds.minY, point.y);
			bounds.maxY = Math.max(bounds.maxY, point.y);
		}
		return bounds;
	}

	public GetPolygonNav2DByName(nodeName: string) {
		for (let j = 0; j < this.AllPolygons.length; j++) {
			let polygon = this.AllPolygons[j];
			if (polygon.node.name == nodeName) {
				return j;
			}
		}
		return -1;
	}

	public RemovePolygon(polygonId: number) {
		for (let j = 0; j < this._allPolygons.length; j++) {
			let polygon = this._allPolygons[j];
			if (polygon.UniqueId == polygonId) {
				this._allPolygons.splice(j, 1);
				break;
			}
		}

		delete this._allInflatePolygons[polygonId];

		//移除交点
		for (let index = this._intersections.length - 1; index >= 0; index--) {
			let element = this._intersections[index];
			if (element.UniqueId1 == polygonId || element.UniqueId2 == polygonId) {
				this._intersections.splice(index, 1);
			}
			else {
				element.IsValid = this.PointIsValid(element.Point);
			}
		}
		this.CreateNodes();
		this.LinkNodes(this._pathNodes);
		if (this.DebugLine) {
			this.PolygonDebugLine.RemoveDebugLine(polygonId);
		}
	}

	private CreateNodes() {
		this._pathNodes = [];
		for (let i = 0; i < this._allPolygons.length; i++) {
			let poly = this._allPolygons[i];
			this.CreateNodesWithPolygon(poly);
		}
	}

	private CreateNodesWithPolygon(poly: Polygon) {
		let inflatedPoints = GeometryUtils.InflatePolygon(poly.Points, 0.1);
		let points: Vec2[] = [];
		for (let i = 0; i < inflatedPoints.length; i++) {
			if (GeometryUtils.PointIsConcave(inflatedPoints, i)) {
				continue;
			}
			if (!this.PointIsValid(inflatedPoints[i])) {
				continue;
			}
			let pathNode = new PathNode(inflatedPoints[i], poly.UniqueId);
			this._pathNodes.push(pathNode);
			points.push(inflatedPoints[i]);
		}
	}

	public CheckLOS(posA: Vec2, posB: Vec2): boolean {
		if (Vec2.squaredDistance(posA, posB) < Number.EPSILON) {
			return true;
		}

		// 计算线段包围盒
		const lineMinX = Math.min(posA.x, posB.x);
		const lineMaxX = Math.max(posA.x, posB.x);
		const lineMinY = Math.min(posA.y, posB.y);
		const lineMaxY = Math.max(posA.y, posB.y);


		for (let index = 0; index < this._allPolygons.length; index++) {
			let poly = this._allPolygons[index];

			// 包围盒快速剔除
			if (lineMaxX < poly.Bounds.minX || lineMinX > poly.Bounds.maxX ||
				lineMaxY < poly.Bounds.minY || lineMinY > poly.Bounds.maxY) {
				continue;
			}

			for (let j = 0; j < poly.Points.length; j++) {
				if (GeometryUtils.SegmentsCross(posA, posB, poly.Points[j], poly.Points[(j + 1) % poly.Points.length])) {
					return false;
				}
			}
		}
		return true;
	}

	private DealOverlapPolygon(a: Polygon, b: Polygon) {
		if (a.Bounds.maxX < b.Bounds.minX || a.Bounds.minX > b.Bounds.maxX ||
			a.Bounds.maxY < b.Bounds.minY || a.Bounds.minY > b.Bounds.maxY) {
			return;
		}

		for (let index = 0; index < a.Points.length; index++) {
			let curPoint = a.Points[index];
			let nextPoint = a.Points[(index + 1) % a.Points.length];
			for (let j = 0; j < b.Points.length; j++) {
				let npCp = b.Points[j];
				let npNp = b.Points[(j + 1) % b.Points.length];
				let result = GeometryUtils.SegmentsIntersect(curPoint, nextPoint, npCp, npNp);
				if (result) {
					this._intersections.push({ Point: result, UniqueId1: a.UniqueId, UniqueId2: b.UniqueId, IsValid: this.PointIsValid(result) });
				}
			}
		}
	}

	private PointInsidePolygon(polygon: Polygon, point: Vec2): boolean {
		if (point.x < polygon.Bounds.minX || point.x > polygon.Bounds.maxX ||
			point.y < polygon.Bounds.minY || point.y > polygon.Bounds.maxY) {
			return false;
		}
		let polyPoints = polygon.Points;
		let xMin = Infinity;
		for (let i = 0; i < polyPoints.length; i++) {
			xMin = Math.min(xMin, polyPoints[i].x);
		}

		const origin = new Vec2(xMin - 0.1, point.y);
		let intersections = 0;

		for (let i = 0; i < polyPoints.length; i++) {
			const pA = polyPoints[i];
			const pB = polyPoints[(i + 1) % polyPoints.length];

			if (GeometryUtils.SegmentsCross(origin, point, pA, pB)) {
				intersections++;
			}
		}

		return (intersections & 1) === 1;
	}

	public GetCloserEdgePoint(point: Vec2): Vec2 {
		let possiblePoints: Vec2[] = [];
		let closerVertex = new Vec2();
		let closerVertexDist = Infinity;
		let temp2 = new Vec2();
		let inflatedPoints: Vec2[] = null;
		for (let p = 0; p < this._allPolygons.length; p++) {
			const poly = this._allPolygons[p];
			inflatedPoints = this._allInflatePolygons[poly.UniqueId].Points;

			for (let i = 0; i < inflatedPoints.length; i++) {
				const a = inflatedPoints[i];
				const b = inflatedPoints[(i + 1) % inflatedPoints.length];

				const originalA = poly.Points[i];
				const originalB = poly.Points[(i + 1) % poly.Points.length];
				let finalPoint = new Vec2();
				Vec2.subtract(finalPoint, point, a);
				Vec2.subtract(temp2, b, a);
				finalPoint.project(temp2);
				finalPoint.add(a);

				if (GeometryUtils.SegmentsCross(point, finalPoint, originalA, originalB) && this.PointIsValid(finalPoint)) {
					possiblePoints.push(finalPoint);
				}

				const dist = Vec2.distance(point, inflatedPoints[i]);
				if (dist < closerVertexDist && this.PointIsValid(inflatedPoints[i])) {
					closerVertexDist = dist;
					closerVertex.set(inflatedPoints[i]);
				}
			}
		}
		for (let index = 0; index < this._intersections.length; index++) {
			if (this._intersections[index].IsValid) {
				possiblePoints.push(new Vec2(this._intersections[index].Point.x, this._intersections[index].Point.y));
			}
		}
		possiblePoints.push(closerVertex);
		possiblePoints.sort((a, b) => Vec2.distance(point, a) - Vec2.distance(point, b));
		return possiblePoints[0];
	}

	public PointIsValid(point: Vec2): boolean {
		for (let i = 0; i < this._allPolygons.length; i++) {
			if (i == 0 ? !this.PointInsidePolygon(this._allPolygons[i], point) : this.PointInsidePolygon(this._allPolygons[i], point)) {
				return false;
			}
		}
		return true;
	}

	public FindPath(start: Vec2, end: Vec2, pathList: Vec2[]): boolean {
		if (this.CheckLOS(start, end)) {
			pathList.push(new Vec2(start));
			pathList.push(new Vec2(end));
			return true;
		}

		let startNode = new PathNode(start);
		let endNode = new PathNode(end);
		this._pathNodes.push(startNode);
		this.LinkStart(startNode, this._pathNodes);
		this._pathNodes.push(endNode);
		this.LinkEnd(endNode, this._pathNodes);
		PolygonNavAStar.GetPath(startNode, endNode, this._pathNodes, pathList);
		let endNodeIndex = this._pathNodes.indexOf(endNode);
		for (let i = 0; i < endNode.links.length; i++) {
			let links = this._pathNodes[endNode.links[i]].links;
			links.splice(links.indexOf(endNodeIndex), 1);
		}
		this._pathNodes.splice(endNodeIndex, 1);
		this._pathNodes.splice(this._pathNodes.indexOf(startNode), 1);
	}

	private LinkNodes(nodes: PathNode[]) {
		for (let i = 0; i < nodes.length; i++) {
			nodes[i].links = [];
			for (let j = 0; j < i; j++) {
				if (this.CheckLOS(nodes[i].pos, nodes[j].pos)) {
					nodes[i].links.push(j);
					nodes[j].links.push(i);
				}
			}
		}
	}

	private LinkStart(start: PathNode, toNodes: PathNode[]) {
		for (let i = 0; i < toNodes.length; i++) {
			if (this.CheckLOS(start.pos, toNodes[i].pos)) {
				start.links.push(i);
			}
		}
	}

	private LinkEnd(end: PathNode, toNodes: PathNode[]) {
		for (let i = 0; i < toNodes.length; i++) {
			if (this.CheckLOS(end.pos, toNodes[i].pos)) {
				end.links.push(i);
				toNodes[i].links.push(toNodes.indexOf(end));
			}
		}
	}
}
